package lua.compiler;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedHashMap;
import java.util.logging.Logger;

import lua.compiler.generated.LuaLexer;
import lua.compiler.generated.LuaParser;
import lua.compiler.tokens.StmtToken;
import lua.compiler.tokens.Tokens;

import org.antlr.runtime.ANTLRInputStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import org.antlr.runtime.tree.Tree;

public class Program {
	// tworzymy glowna funkcje, do ktorej przypisujemy caly kod spoza def-ow
	static Function main;
	static Logger logger = Logger.getAnonymousLogger();
	
	private static boolean ExecuteCommand(String command)
	{
		logger.info("Executing: " + command);
		Runtime rt = Runtime.getRuntime();
        Process p;
		try {
			p = rt.exec(command);
			BufferedReader pbr = new BufferedReader(new InputStreamReader(p.getInputStream()));
		 
			String s;
		    while ((s = pbr.readLine()) != null) {
		    	System.out.println(s);
		    }
		 
		    pbr.close();
			p.waitFor();
			if (p.exitValue() == 0)
				logger.info("Executed: OK");
			else {
				logger.warning("Executed: Error");
				return false;
			}
		} catch (IOException e) {
			logger.severe("Error: " + e.getMessage());
			return false;
		} catch (InterruptedException e) {
			logger.severe("Error: " + e.getMessage());
			return false;
		}
		
		return true;
	}

	private static LuaParser setupParser(String inputPath) {
		ANTLRInputStream input = null;
			
		// ustawianie wszystkiego dla ANTLRa
		logger.info("Tworze strumien wejsciowy dla ANTLRa...");	
		try {
			input = new ANTLRInputStream(
					new FileInputStream(inputPath));
		} catch (FileNotFoundException e1) {
			logger.severe("Nie znaleziono pliku wejsciowego " + inputPath);
			System.exit(-1);
		} catch (IOException e1) {
			logger.severe("Nie udalo sie odczytac pliku wejsciowego " + inputPath);
			System.exit(-1);
		}

		logger.info("Tworze lexer...");
		LuaLexer lexer = new LuaLexer(input);	
		
		logger.info("Tworze parser...");
		CommonTokenStream tokenStream = new CommonTokenStream(lexer);
		LuaParser parser = new LuaParser(tokenStream);
		
		// zebysmy mogli z dowolnego miejsca w kodzie pobierac nazwy tokenow
		Tokens.setTokens(parser.getTokenNames());
		
		return parser;
	}

	private static void printOutputToFile(DataOutputStream out) {
		BufferedReader br;
		String s;
		try {
			// poczatek pliku (zawsze taki sam, nie ma sensu go generowac)
			br = new BufferedReader(new InputStreamReader(
					new FileInputStream("src/il/prolog.il")));

			while ((s = br.readLine()) != null) {
				out.writeBytes(s);
				out.writeBytes("\r\n");
			}

			Functions.addFunction(main);

			// caly kod jest podzielony na funkcje - wypisujemy je do pliku wyjsciowego
			for (Function function : Functions.getFunctions()) {
				out.writeBytes(function.toString());
			}

			// i koncowka + konstruktor (zawsze taki sam, bo caly kod leci do Main i naszych funkcji)
			br = new BufferedReader(new InputStreamReader(
					new FileInputStream("src/il/epilog.il")));
			while ((s = br.readLine()) != null) {
				out.writeBytes(s);
				out.writeBytes("\r\n");
			}

			out.close();
		}
		catch (IOException e) {
			logger.severe("Blad IO: " + e.getMessage());
			System.exit(-1);
		}
		logger.info("Wyjscie zapisane");
	}
	
	public static void main(String[] args) {
		if (args.length < 2) {
			System.err.println("Uzycie: java compiler.jar <in.lua> <out.il>");
			System.exit(-1);
		}
		
		String inputPath = args[0];
		String outputPath = args[1];
		DataOutputStream out = null;

		LuaParser parser = setupParser(inputPath);

		// plik, do ktorego wypisujemy wygenerowany kod
		try {
			out = new DataOutputStream(new FileOutputStream(outputPath));
		} catch (FileNotFoundException ex) {
			logger.severe("Nie udalo sie stworzyc pliku wyjsciowego " + outputPath);
			System.exit(-1);
		}

		main = new Function("Main", new LinkedHashMap<String, CtsType>());

		try {
			LuaParser.program_return program = parser.program();
			Tree tree = (Tree)program.getTree();

			TreePrinter.printTree(tree, 0);

			StmtToken stmtToken = new StmtToken();

			// jesli jest tylko jedna instrukcja
			if (tree.getText() != null) {
				stmtToken.parse(tree, main);
			}
			else {
				// wiecej instrukcji - przegladamy wszystkie dzieci
				for (int i = 0; i < tree.getChildCount(); i++) {
					stmtToken.parse(tree.getChild(i), main);
				}
			}
		}
		catch (RecognitionException ex) {
			ex.printStackTrace();
			System.exit(-1);
		}
		catch (CompilerException ex) {
			File f = new File(inputPath);
			System.err.println(
					f.getName() + ":" + ex.getTree().getLine() + " " + ex.getMessage());
			System.exit(-1);
		}
		
		printOutputToFile(out);
		
		logger.info("Uruchamiam ilasm...");
		boolean assembled = ExecuteCommand("../External/ilasm/ilasm.exe " + outputPath);
		
		if (assembled) {
			String exeFileName = outputPath.substring(0, outputPath.length() - 3) + ".exe";
			boolean verified = ExecuteCommand("../External/peverify/PEVerify.exe " + exeFileName);
			if (verified){				
				logger.info("Uruchamiam skompilowany program...");
				ExecuteCommand(exeFileName);
			}
		}		
	}
}